/*
 *	Yalala
 *	
 *	Copyright © MediaMonks B.V.
 *	All rights reserved.
 *	
 *	Redistribution and use in source and binary forms, with or without
 *	modification, are permitted provided that the following conditions are met:
 *	1. Redistributions of source code must retain the above copyright
 *	   notice, this list of conditions and the following disclaimer.
 *	2. Redistributions in binary form must reproduce the above copyright
 *	   notice, this list of conditions and the following disclaimer in the
 *	   documentation and/or other materials provided with the distribution.
 *	3. All advertising materials mentioning features or use of this software
 *	   must display the following acknowledgement:
 *	   This product includes software developed by MediaMonks B.V.
 *	4. Neither the name of MediaMonks B.V. nor the
 *	   names of its contributors may be used to endorse or promote products
 *	   derived from this software without specific prior written permission.
 *	
 *	THIS SOFTWARE IS PROVIDED BY MEDIAMONKS B.V. ''AS IS'' AND ANY
 *	EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *	WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *	DISCLAIMED. IN NO EVENT SHALL MEDIAMONKS B.V. BE LIABLE FOR ANY
 *	DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *	ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *	
 */

package com.mediamonks.yalala
{
	import flash.text.TextFieldAutoSize;
	import temple.codecomponents.label.CodeLabel;
	import nl.acidcats.yalog.Yalog;
	import nl.acidcats.yalog.common.Functions;
	import nl.acidcats.yalog.common.Levels;
	import nl.acidcats.yalog.common.MessageData;
	import nl.acidcats.yalog.util.YaLogConnector;

	import temple.codecomponents.style.CodeStyle;
	import temple.codecomponents.tooltip.CodeToolTip;
	import temple.core.Temple;
	import temple.core.debug.log.Log;
	import temple.core.display.CoreSprite;
	import temple.net.sharedobject.SharedObjectService;
	import temple.ui.focus.TabFocusManager;
	import temple.ui.layout.liquid.LiquidStage;
	import temple.ui.tooltip.ToolTip;
	import temple.utils.StageSettings;
	import temple.utils.TimeUtils;
	import temple.utils.keys.KeyManager;
	import temple.utils.types.DateUtils;

	import com.google.analytics.GATracker;
	import com.mediamonks.yalala.data.filters.ConnectionFilter;
	import com.mediamonks.yalala.data.filters.LevelFilter;
	import com.mediamonks.yalala.data.filters.StringFilter;
	import com.mediamonks.yalala.data.vo.SettingsData;
	import com.mediamonks.yalala.data.vo.StringFilterData;
	import com.mediamonks.yalala.net.ReceiverConnection;
	import com.mediamonks.yalala.net.ReceiverConnectionEvent;
	import com.mediamonks.yalala.ui.Footer;
	import com.mediamonks.yalala.ui.Header;
	import com.mediamonks.yalala.ui.UIEvent;
	import com.mediamonks.yalala.ui.Viewer;
	import com.mediamonks.yalala.ui.connections.ConnectionButton;
	import com.mediamonks.yalala.ui.connections.Connections;

	import flash.events.Event;
	import flash.events.StatusEvent;
	import flash.geom.Point;
	import flash.net.FileReference;
	import flash.net.LocalConnection;
	import flash.net.registerClassAlias;
	import flash.system.System;
	import flash.utils.ByteArray;
	import flash.utils.getQualifiedClassName;
	import flash.utils.getTimer;

	/**
	 * @author Thijs Broerse
	 */
	[SWF(backgroundColor="#333333", frameRate="31", width="640", height="480")]
	public class Yalala extends CoreSprite
	{
		/**
		 * Settings
		 */
		YaLogConnector.connect("Yalala");
		Temple.registerObjectsInMemory = false;
		Log.stackTrace = true;
		Log.showTrace = true;
		Yalog.showTrace = false;
		private var _receiver:ReceiverConnection;
		private var _transmitter:LocalConnection;
		private var _viewer:Viewer;
		private var _settings:SettingsData;
		private var _connections:Connections;
		private var _connectionFilter:ConnectionFilter;
		private var _tracker:GATracker;

		public function Yalala()
		{
			this.init();
		}

		private function init():void
		{
			new StageSettings(this);
			KeyManager.init(this.stage);

			/**
			 * Settings
			 */
			registerClassAlias(getQualifiedClassName(SettingsData), SettingsData);
			this._settings = SharedObjectService.getInstance().getProperty('settings') as SettingsData;
			if (!this._settings)
			{
				this._settings = new SettingsData();
				this._settings.reset();
				SharedObjectService.getInstance().setProperty('settings', this._settings);
			}

			this.createUI();

			this.initReceiver();
			this.initTransmitter();

			try
			{
				this._tracker = new GATracker(this, 'UA-2565066-5');
				this._tracker.trackPageview("Yalala");
			}
			catch (error:Error)
			{
				// ignore
			}
		}

		private function createUI():void
		{
			// Overwrite some style settings
			CodeStyle.textColor = 0xdddddd;
			CodeStyle.textFormat.bold = true;

			// viewer
			this._viewer = new Viewer(this._settings);
			this.addChild(this._viewer);
			this._viewer.left = 0;
			this._viewer.right = 0;
			this._viewer.top = 46;
			this._viewer.bottom = 34;
			this._viewer.filter.add(new LevelFilter(this._settings.levels));

			var messageFilter:StringFilter = new StringFilter(SettingsData.TEXT, new StringFilterData());
			var senderFilter:StringFilter = new StringFilter(SettingsData.SENDER, new StringFilterData());
			var idFilter:StringFilter = new StringFilter(SettingsData.SENDERID, new StringFilterData());
			var stackFilter:StringFilter = new StringFilter(SettingsData.STACKTRACE, new StringFilterData());

			this._viewer.filter.add(messageFilter);
			this._viewer.filter.add(senderFilter);
			this._viewer.filter.add(idFilter);
			this._viewer.filter.add(stackFilter);

			var focusManager:TabFocusManager = new TabFocusManager(true);

			// header
			var header:Header = new Header(this._settings, messageFilter, senderFilter, idFilter, stackFilter);
			focusManager.add(header.focusManager);
			this.addChild(header);
			header.addEventListener(UIEvent.UPDATE, this.handleUIEvent);

			// connection
			this._connections = new Connections();
			focusManager.add(this._connections.focusManager);
			this._connections.y = 25;
			this.addChild(this._connections);
			this._connections.addEventListener(Event.CHANGE, this.handleConnectionTabBarChange);
			this._connections.addEventListener(Event.RESIZE, this.handleConnectionResize);
			this._connections.addEventListener(UIEvent.CLEAR_CONNECTION, this.handleUIEvent);
			this._connectionFilter = new ConnectionFilter();

			this._viewer.filter.add(this._connectionFilter);

			// footer
			var footer:Footer = new Footer(this._settings);
			this.addChild(footer);
			focusManager.add(footer.focusManager);
			footer.left = 10;
			footer.bottom = 5;
			footer.addEventListener(UIEvent.CLEAR, this.handleUIEvent);
			footer.addEventListener(UIEvent.COPY, this.handleUIEvent);
			footer.addEventListener(UIEvent.EXPORT, this.handleUIEvent);
			footer.addEventListener(UIEvent.UPDATE, this.handleUIEvent);

			LiquidStage.getInstance().minimalHeight = 200;
			LiquidStage.getInstance().minimalWidth = 860;

			// tooltip
			var tooltip:CodeToolTip = new CodeToolTip();
			this.addChild(tooltip);
			ToolTip.clip = tooltip;
			ToolTip.showDelay = 1000;
			ToolTip.offset = new Point(0, -20);
			ToolTip.minimumStageMargin = 4;
			
			var label:CodeLabel = new CodeLabel("<a href='https://code.google.com/p/yalala/' target='_blank'>Yalala project site</a>", false, TextFieldAutoSize.LEFT, true);
			this.addChild(label);
			label.alpha = .25;
			label.right = 4;
			label.top = 4;
			
			label = new CodeLabel("by <a href='http://www.mediamonks.com' target='_blank'>MediaMonks</a>", false, TextFieldAutoSize.LEFT, true);
			this.addChild(label);
			label.alpha = .25;
			label.right = 4;
			label.bottom = 7;
		}

		private function handleConnectionResize(event:Event):void
		{
			this._viewer.top = this._connections.contentWidth > this._connections.width ? 60 : 46;
			this._viewer.update();
		}

		private function handleConnectionTabBarChange(event:Event):void
		{
			this._connectionFilter.connectionIds = this._connections.value;
			this._viewer.updateAllMessages();
		}

		private function initReceiver():void
		{
			this._receiver = new ReceiverConnection();

			var receiverConnected:Boolean = false;
			try
			{
				receiverConnected = true;
				this._receiver.connect(Functions.CHANNEL);
			}
			catch (e:ArgumentError)
			{
				receiverConnected = false;
			}

			if (!receiverConnected)
			{
				// display error message
				this.addMessage(new MessageData("-- CONNECTION FAILED --", Levels.ERROR));
			}
			else
			{
				this.addMessage(new MessageData("-- CONNECTED --", Levels.INFO));
				this._receiver.addEventListener(ReceiverConnectionEvent.PING_RECEIVED, this.handlePingReceived);
				this._receiver.addEventListener(ReceiverConnectionEvent.DATA_RECEIVED, this.handleDataReceived);
			}
		}

		private function addMessage(messageData:MessageData):void
		{
			this._viewer.addMessage(messageData);
			this._connections.addConnection(messageData.connectionId, messageData.connectionName);
		}

		/**
		 * Initialize LocalConnection to pong any present applications
		 */
		private function initTransmitter():void
		{
			this._transmitter = new LocalConnection();
			this._transmitter.addEventListener(StatusEvent.STATUS, this.handleTransmitterStatusEvent);
			for (var i:Number = 1;i < Functions.MAX_CHANNEL_COUNT;i++)
			{
				this._transmitter.send(Functions.CHANNEL_PING + i, Functions.FUNC_PONG);
			}
		}

		private function handlePingReceived(event:ReceiverConnectionEvent):void
		{
			this.addMessage(new MessageData("Ping received on channel '" + event.channel + "'", Levels.STATUS, getTimer(), this.toString(), 0, null, event.data.connectionId));
			this.addMessage(new MessageData("-- NEW LOG STARTED @ " + DateUtils.format("H:i:s") + " --", Levels.INFO, getTimer(), this.toString(), 0, null, event.data.connectionId));

			try
			{
				this._transmitter.send(event.channel, Functions.FUNC_PONG);
			}
			catch (error:Error)
			{
			}
		}

		private function handleDataReceived(event:ReceiverConnectionEvent):void
		{
			this.addMessage(event.data);
		}

		/**
		 * Handle status events form sender connection
		 */
		private function handleTransmitterStatusEvent(event:StatusEvent):void
		{
		}

		private function handleUIEvent(event:UIEvent):void
		{
			switch (event.type)
			{
				case UIEvent.CLEAR:
					this._viewer.clear();
					this._connections.clear();
					break;
				case UIEvent.COPY:
					this.copy();
					break;
				case UIEvent.EXPORT:
					this.export();
					break;
				case UIEvent.UPDATE:
					this._viewer.updateAllMessages();
					break;
				case UIEvent.CLEAR_CONNECTION:
					if (event.target is ConnectionButton)
					{
						this._viewer.clearConnection((event.target as ConnectionButton).id);
						this._connections.clearConnection((event.target as ConnectionButton).id);
					}
					break;
				default:
					this.logWarn("handleFooterEvent: unhandled FooterEvent: '" + event.type + "'");
					break;
			}
		}

		private function copy():void
		{
			var string:String = "";
			var messages:Vector.<MessageData> = this._viewer.filtered;

			var leni:int = messages.length;
			for (var i:int = 0; i < leni; i++)
			{
				string += !isNaN(messages[i].time) ? TimeUtils.formatTime(messages[i].time) : "   --    ";
				string += "\t";
				string += Levels.NAMES[messages[i].level] + ':';
				string += "\t";
				string += messages[i].text;

				if (messages[i].sender != null)
				{
					string += "\t\t--" + messages[i].sender;
					if (messages[i].senderId) string += " | id:" + messages[i].senderId;
					if (messages[i].stackTrace) string += " | " + messages[i].stackTrace;
				}

				string += "\n";
			}
			System.setClipboard(string);
		}

		private function export():void
		{
			var string:String = "";
			const separator:String = ";";

			// header
			string += '"time"';
			string += separator;
			string += '"miliseconds"';
			string += separator;
			string += '"level"';
			string += separator;
			string += '"text"';
			string += separator;
			string += '"sender"';
			string += separator;
			string += '"senderId"';
			string += separator;
			string += '"stackTrace"';
			string += "\n";

			var messages:Vector.<MessageData> = this._viewer.filtered;

			var leni:int = messages.length;
			for (var i:int = 0; i < leni; i++)
			{
				if (!isNaN(messages[i].time)) string += TimeUtils.formatTime(messages[i].time);
				string += separator;
				if (!isNaN(messages[i].time)) string += messages[i].time;
				string += separator;
				string += '"' + Levels.NAMES[messages[i].level] + '"';
				string += separator;
				string += '"' + messages[i].text.replace(/"/g, '""') + '"';
				string += separator;
				if (messages[i].sender) string += '"' + messages[i].sender.replace(/"/g, '""') + '"';
				string += separator;
				string += messages[i].senderId;
				string += separator;
				if (messages[i].stackTrace) string += '"' + messages[i].stackTrace + '"';
				string += "\n";
			}

			var byteArray:ByteArray = new ByteArray();
			byteArray.writeUTFBytes(string);

			var fileReference:FileReference = new FileReference();
			fileReference.save(byteArray, "export.csv");
		}
	}
}
